#include "stdafx.h"
#include "MonitorProcess.h"
#include "MessageSerializerInterface.h"
#include "ProcessManagerInterface.h"
#include "Utils.h"

CMonitorProcess* g_MonitorProcess = nullptr;

const int BUFFER_SIZE = 2000;
const wchar_t* SHARED_MEMORY_NAME = L"MySharedMemory";
const wchar_t* SEMAPHORE_FIRST_SENDER_NAME = L"MyFirstSenderSemaphore";
const wchar_t* SEMAPHORE_FIRST_RECEIVER_NAME = L"MyFirstReceiverSemaphore";
const wchar_t* SEMAPHORE_SECOND_SENDER_NAME = L"MyFirstSenderSemaphore";
const wchar_t* SEMAPHORE_SECOND_RECEIVER_NAME = L"MyFirstReceiverSemaphore";

//**********************************************************
//function:
LRESULT WindowProcW(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (msg != WM_USER + 1) return DefWindowProc(hwnd, msg, wParam, lParam);
	return g_MonitorProcess->__receiveProcessMessage(hwnd, msg, wParam, lParam);
}

//**********************************************************
//function:
CMonitorProcess::CMonitorProcess(const std::string& vTargetProcessName, const std::string& vTargetCrashPath) : m_WindowHandle(NULL), m_StatusType(EStatusType::None), m_TargetProcessName(vTargetProcessName), m_TargetCrashPath(vTargetCrashPath)
{
	m_ThreadHandle = CreateThread(NULL, 0, __crashReportingThreadProc, this, 0, &m_ThreadId);
}

//**********************************************************
//function:
CMonitorProcess::~CMonitorProcess()
{

}

//**********************************************************
//function:
void CMonitorProcess::dumpSolutionDir(std::string& vioSolutionDir)
{
	wchar_t Buffer[9999];
	GetModuleFileName(NULL, Buffer, sizeof(Buffer));

	std::wstring WExePath(Buffer);
	std::string ExePathStr = "";
	for (wchar_t wch : WExePath)
		ExePathStr += static_cast<char>(wch);
	ExePathStr = ExePathStr.substr(0, ExePathStr.find_last_of("\\/"));

	std::filesystem::path TargetBasePath(ExePathStr);
	vioSolutionDir = TargetBasePath.parent_path().parent_path().string();
	vioSolutionDir += "\\";
}

//**********************************************************
//function:
void CMonitorProcess::createMonitorProcessInstance(const std::string& vTargetProcessName, const std::string& vTargetCrashPath)
{
	_ASSERTE(g_MonitorProcess == nullptr);
	g_MonitorProcess = new CMonitorProcess(vTargetProcessName, vTargetCrashPath);
}

//**********************************************************
//function:
void CMonitorProcess::deleteMonitorProcessInstance()
{
	if (g_MonitorProcess)
	{
		delete(g_MonitorProcess);
		g_MonitorProcess = nullptr;
	}
}

//**********************************************************
//function:
CMonitorProcess* CMonitorProcess::getMonitorProcessInstance()
{
	_ASSERTE(g_MonitorProcess);
	return g_MonitorProcess;
}

//**********************************************************
//function:
void CMonitorProcess::createWindowAndLoop()
{
	WNDCLASS wc = { 0 };
	wc.style = CS_DBLCLKS; // We want to receive double clicks
	wc.lpfnWndProc = StaticWindowProcW;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = GetModuleHandle(NULL);
	wc.hCursor = NULL;	// We manage the cursor ourselves
	wc.hbrBackground = NULL; // Transparent
	wc.lpszMenuName = NULL;
	wc.lpszClassName = L"MonitorWindowClass";
	if (!::RegisterClass(&wc))
	{
		MessageBox(NULL, TEXT("Window Registration Failed!"), TEXT("Error!"), MB_ICONEXCLAMATION | MB_OK);
		return;
	}
	m_WindowHandle = CreateWindowW(wc.lpszClassName, L"MonitorWindow", 0, 0, 0, 0, 0, HWND_MESSAGE, 0, GetModuleHandle(NULL), 0);
	MSG msg;
	while (GetMessageW(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessageW(&msg);
	}
}

//**********************************************************
//function:
LRESULT CMonitorProcess::StaticWindowProcW(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (uMsg != WM_USER + 1) return DefWindowProc(hwnd, uMsg, wParam, lParam);
	return g_MonitorProcess->__receiveProcessMessage(hwnd, uMsg, wParam, lParam);
}

//**********************************************************
//function:
LRESULT CMonitorProcess::__receiveProcessMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (wParam)
	{
	case (WPARAM)EMessageType::Start:
		g_MonitorProcess->__onStartRecord(EStatusType(lParam));
		break;
	case (WPARAM)EMessageType::Running:
		g_MonitorProcess->__onReceiveMessage();
		break;
	case (WPARAM)EMessageType::End:
		m_IsRunning = false;
		g_MonitorProcess->__onEndRecord();
		break;
	case (WPARAM)EMessageType::Status:
		g_MonitorProcess->__onStatusChanged(EStatusType(lParam));
		break;
	default:
		break;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

//**********************************************************
//function:
DWORD __stdcall CMonitorProcess::__crashReportingThreadProc(LPVOID pThis)
{
	CMonitorProcess* This = (CMonitorProcess*)pThis;
	return This->__run();
}

//**********************************************************
//function:
void CMonitorProcess::__onStartRecord(MessageProcess::EStatusType vType)
{
	m_StatusType = vType;

	m_SharedMemoryHandle = OpenFileMapping(FILE_MAP_READ, FALSE, SHARED_MEMORY_NAME);
	m_SharedMemory = MapViewOfFile(m_SharedMemoryHandle, FILE_MAP_READ, 0, 0, 0);
	m_FirstSenderSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, SEMAPHORE_FIRST_SENDER_NAME);
	m_FirstReceiverSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, SEMAPHORE_FIRST_RECEIVER_NAME);
	m_SecondSenderSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, SEMAPHORE_SECOND_SENDER_NAME);
	m_SecondReceiverSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, SEMAPHORE_SECOND_RECEIVER_NAME);

	m_IsRelease = false;
}

//**********************************************************
//function:
void CMonitorProcess::__onReceiveMessage(bool vIsEndOfSend)
{
	if (m_StatusType == EStatusType::OnlyRecord || m_StatusType == EStatusType::RecordAndTest)
	{
		// Wait for sender to finish with the first buffer
		if (!vIsEndOfSend)
		{
			if (!m_IsRelease)
				WaitForSingleObject(m_FirstReceiverSemaphore, INFINITE);
			else
				WaitForSingleObject(m_SecondReceiverSemaphore, INFINITE);
		}

		SMessage* Msg = m_IsRelease ? static_cast<SMessage*>(m_SharedMemory) + BUFFER_SIZE : static_cast<SMessage*>(m_SharedMemory);
		for (int i = 0; i < BUFFER_SIZE; i++)
		{
			SMessage message = Msg[i];
			if (message._MessageID >= m_MessageQueue.size())
			{
				m_MessageQueue.push_back(message);
				std::cout << message._MessageID << " " << message._MessageStr << std::endl;
			}
		}

		if (!vIsEndOfSend)
		{
			if (!m_IsRelease)
				ReleaseSemaphore(m_FirstSenderSemaphore, 1, nullptr);
			else
				ReleaseSemaphore(m_SecondSenderSemaphore, 1, nullptr);
		}

		m_IsRelease = !m_IsRelease;
	}
}

//**********************************************************
//function:
void CMonitorProcess::__onEndRecord()
{
	if (m_StatusType == EStatusType::OnlyRecord || m_StatusType == EStatusType::RecordAndTest)
	{
		g_MonitorProcess->__onReceiveMessage(true);
		m_IsRelease = false;

		std::cout << "The number of messages : " << m_MessageQueue.size() << std::endl;
		std::string SolutionDir;
		dumpSolutionDir(SolutionDir);
		__dumpMessageQueue(SolutionDir + "Log\\");
		m_MessageQueue.clear();
	}
	if (m_SharedMemory) UnmapViewOfFile(m_SharedMemory);
	if (m_SharedMemoryHandle) CloseHandle(m_SharedMemoryHandle);
	if (m_FirstSenderSemaphore) CloseHandle(m_FirstSenderSemaphore);
	if (m_FirstReceiverSemaphore) CloseHandle(m_FirstReceiverSemaphore);
	if (m_SecondSenderSemaphore) CloseHandle(m_SecondSenderSemaphore);
	if (m_SecondReceiverSemaphore) CloseHandle(m_SecondReceiverSemaphore);
}

//**********************************************************
//function:
void CMonitorProcess::__onStatusChanged(EStatusType vType)
{
	m_StatusType = vType;
}

//**********************************************************
//function:
void CMonitorProcess::__dumpMessageQueue(const std::string& vFolderPath)
{
	std::string TargetFilePathString = Utils::generateFilesBasedOnTime("", ".bin");
	CMessageSerializerInterface::saveMessages((vFolderPath + "\\" + TargetFilePathString).c_str(), m_MessageQueue);
}

//**********************************************************
//function:
DWORD CMonitorProcess::__run()
{
	HANDLE hProcess = nullptr;
	while (m_IsRunning)
	{
		if (!ProcessManager::CProcessManagerInterface::IsProcessRunning(m_TargetProcessName))
		{
			g_MonitorProcess->__onEndRecord();
			std::cout << "Process B has crashed or exited." << std::endl;
			__handleCrash();
			break;
		}
		CloseHandle(hProcess);
		hProcess = nullptr;
		Sleep(1000);
	}
	return 0;
}

//**********************************************************
//function:
void CMonitorProcess::__handleCrash()
{
	std::string SolutionDir;
	dumpSolutionDir(SolutionDir);
	std::string FolderPath = Utils::generateFilesBasedOnTime(SolutionDir + "Saved\\Crashes\\", "");
	_ASSERTE(!std::filesystem::exists(FolderPath));
	if (std::filesystem::create_directories(FolderPath))
	{
		__dumpMessageQueue(FolderPath);
		__copyMiniDump(FolderPath);
	}
}

//**********************************************************
//function:
bool CMonitorProcess::__copyMiniDump(const std::string& vFolderPath)
{
	std::string UEDmpFolderPath;
	__dumpUECrashFolderPath(UEDmpFolderPath);
	if (std::filesystem::exists(UEDmpFolderPath) && std::filesystem::is_directory(UEDmpFolderPath))
	{
		std::filesystem::copy(UEDmpFolderPath, vFolderPath);
		return true;
	}
	return false;
}

//**********************************************************
//function:
void CMonitorProcess::__dumpUECrashFolderPath(std::string& vioUECrashPath)
{
	std::filesystem::path NewestFolder;
	std::chrono::system_clock::duration maxTimeDifference = std::chrono::seconds(5);

	if (std::filesystem::exists(m_TargetCrashPath) && std::filesystem::is_directory(m_TargetCrashPath))
	{
		std::chrono::system_clock::time_point latestTime = std::chrono::system_clock::from_time_t(0);

		for (const auto& entry : std::filesystem::directory_iterator(m_TargetCrashPath))
		{
			if (std::filesystem::is_directory(entry))
			{
				std::filesystem::file_time_type folderTime = std::filesystem::last_write_time(entry);
				std::chrono::system_clock::time_point folderTimePoint = std::chrono::clock_cast<std::chrono::system_clock>(folderTime);
				if (folderTimePoint > latestTime)
				{
					latestTime = folderTimePoint;
					NewestFolder = entry.path();
				}
			}
		}

		if (!NewestFolder.empty())
		{
			vioUECrashPath = NewestFolder.string();
		}
		else
		{
			std::cout << "No recent folders found." << std::endl;
		}
	}
	else
	{
		std::cerr << "Target path is not a valid directory." << std::endl;
	}
}